Week 12 — Networking and Communication

Dec 3, 2025 · Networking Machine PCB
I had some errands to run this week and had to skip class. Between Networking and Communication, I found the latter to be more fascinating. In fact, I had always wanted to explore how RFID/NFC works for a very long time and never quite had the right opportunity. So here we go.

To start with, some basic information I found online and organized by ChatGPT:

What is RFID?

RFID (Radio-Frequency Identification) is a broad family of technologies where tags are read wirelessly by a reader using radio waves. RFID systems exist in several frequency bands:

  • LF (Low Frequency, ~125–134 kHz) – short range, often used for simple access badges or hotel keys.
  • HF (High Frequency, 13.56 MHz) – used for contactless cards and tickets.
  • UHF (Ultra High Frequency, ~860–960 MHz) – longer range, common in logistics and warehouse tracking.

Many RFID tags are simple and mostly one-way: the reader powers the tag and reads an ID or small amount of data. It is great for fast, large-scale identification.

What is NFC?

NFC (Near Field Communication) is a specific subset of HF RFID that also operates at 13.56 MHz, but is designed for close-range, secure, user-initiated interactions.

  • Very short range (a few centimeters).
  • Supports two-way communication between devices.
  • Enables card emulation, so a phone or watch can behave like a transit card or credit card.
  • Used for contactless payments, transit gates, and phone-to-device tapping.

Simply put, every NFC system is RFID, but not every RFID system is NFC.

Many years ago I was in Tokyo and I had this friend who at the time worked at NEC Japan, the very company behind the NFC tech for every subway gate. At the time he was very proud and bragged about the advance technology that Tokyo used and how fast those doors can slide open within hundreds of a second and no passenger would need to fear they may be blocked by the bar and got hurt. He briefly mentioned the NFC then and I vaguely remembered the technology that was used. Many years later, I have been to way more cities and countries than i was then and having compared all the subway gates. I guess I had to admit, in terms of gate opening speed, Tokyo is the best in the world and beating all other countries by a lot.

This memory led me to think about how the subway gates work in different cities, especially comparing Boston and Tokyo, since I have used both extensively. After a bit of research, here's a breakdown of how the systems operate in Boston and Tokyo:

Boston vs Tokyo: How the Gates Work

Tokyo (Suica / PASMO)

  • Uses IC cards (Suica, PASMO) and mobile versions on phones and watches.
  • Underlying tech: FeliCa (NFC-F) on HF RFID.
  • Tap once at entry and once at exit; the same card works across most trains, subways, and many buses in the Greater Tokyo area.

Boston (MBTA / “the T”)

  • Uses the CharlieCard, a contactless smart card based on MIFARE (HF RFID).
  • Historically, you tapped the CharlieCard at entry only; subway fares are flat within the system.
  • As of 2024, the MBTA has begun rolling out a new system where riders can tap contactless credit/debit cards or phones directly at subway and bus gates (“tap-to-pay”, Automated Fare Collection 2.0).

Quick Technical Comparison

Feature Tokyo (Suica / PASMO) Boston (MBTA)
Primary card Suica / PASMO IC cards CharlieCard smart card
Core technology FeliCa (NFC-F, HF RFID) MIFARE-based HF RFID
Phone & watch support Mobile Suica / PASMO, Apple Pay Contactless credit/debit, mobile wallets (AFC 2.0)
User-facing terminology “IC card” (rarely called NFC) “CharlieCard” / “tap-to-pay”
Network coverage Most rail & bus operators across Greater Tokyo MBTA subway & buses; commuter rail/ferry via phased rollout

In summary, both cities are using HF RFID smart cards at their gates. Tokyo's system is built around FeliCa-based NFC cards (Suica/PASMO), while Boston's system historically relied on a MIFARE-based transit card (CharlieCard) and is now adding NFC-based open-loop payments with bank cards and phones.

I referred to a few online resources to gather this information, and one particular site I found really helpful Link Here Another document I found really helpful is the datasheet of XIAO ESP32S3 Download Here RFID-RCC522's spec documentHere.
Got the RC522 from Anthony. I suppose for this simple task a SAMD21 would do.

Code Used:

Arduino / C++ XIAO SAMD21 + RC522 RFID (SPI) UID Reader

#include <SPI.h>
#include <MFRC522.h>

// Your wiring:
const uint8_t SS_PIN  = 6;   // RC522 SDA pin -> XIAO D6 (Chip Select)
const uint8_t RST_PIN = 7;   // RC522 RST pin -> XIAO D7

MFRC522 rfid(SS_PIN, RST_PIN);  // Create MFRC522 instance

void setup() {
  // Start serial for debugging
  Serial.begin(115200);
  while (!Serial) {
    ; // Wait for Serial on SAMD21 (so you can see messages)
  }

  Serial.println("Booting...");
  Serial.println("Initializing SPI and RC522...");

  // Initialize SPI bus (D8=SCK, D9=MISO, D10=MOSI on XIAO SAMD21)
  SPI.begin();

  // Initialize RC522 reader
  rfid.PCD_Init();
  delay(50);

  // Optional: Show reader details
  Serial.print("MFRC522 Firmware version: 0x");
  byte v = rfid.PCD_ReadRegister(MFRC522::VersionReg);
  Serial.println(v, HEX);

  if (v == 0x00 || v == 0xFF) {
    Serial.println("WARNING: Could not communicate with RC522.");
    Serial.println("Check wiring and power (3.3V, GND, SS=D6, RST=D7, MOSI=D10, MISO=D9, SCK=D8).");
  } else {
    Serial.println("RC522 initialized successfully. Present a card to the reader.");
  }
}

void loop() {
  // Look for new cards
  if (!rfid.PICC_IsNewCardPresent()) {
    return; // No new card
  }

  // Select one of the cards
  if (!rfid.PICC_ReadCardSerial()) {
    return; // Read error
  }

  Serial.println("Card detected!");

  // Print UID in HEX
  Serial.print("UID (HEX): ");
  for (byte i = 0; i < rfid.uid.size; i++) {
    if (rfid.uid.uidByte[i] < 0x10) {
      Serial.print("0"); // leading zero
    }
    Serial.print(rfid.uid.uidByte[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  // Print card type
  MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak);
  Serial.print("Card type: ");
  Serial.println(rfid.PICC_GetTypeName(piccType));

  Serial.println("------------------------");

  // Halt the card and stop encryption (good practice)
  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();

  delay(200);  // small delay so output is readable
}
  
However in terms of actually soldering this thing. I came to the lab pretty late and they were about to close. Given the relatively simple connection, I improvised a bit and took a few wire just to see if the thing works. I can always come back and solder them together. But frankly speaking, who needs a PCB board when simple wire would do...
After I figured out how it works and which pins goes to which pins on the reader chip, it goes pretty straight forward from there. Learned a bit about how UID works and wrote code (ChatGPT helped a lot...) to read from the card Anthony give me. So obviously UID is kind of fixed and cannot be changed. (I did read about this article saying there's some Chinese "magical" version of card that allows the UID to be changed...Of course). This is really fun and I am actually thinking of copying my girl friend's apartment keyfob. I believe it's the same mechnism. I wonder if how the identify is accessed. If just UID I guess we just cannot copy the key fob, but if they read other contect, I may have a chance to duplicate her keyfob using this one card I have. I will try this later(Maybe after final project...)
Improved the code. Read the UID from the card, and then match with a predetermined database. If the UID can be found in the database, return "Access Granted"
Read the card
Writing Completed. Adding HTMAA 2025 in ASCII to the card.
Arduino / C++ XIAO SAMD21 + RC522 RFID (Access Control by UID)

    #include <SPI.h>
    #include <MFRC522.h>

    const uint8_t SS_PIN  = 6;   // RC522 SDA -> XIAO D6
    const uint8_t RST_PIN = 7;   // RC522 RST -> XIAO D7

    MFRC522 rfid(SS_PIN, RST_PIN);

    // 👉 Replace this with the UID you want to "allow"
    byte allowedUID[] = {0x47, 0x71, 0xB5, 0x11};
    byte allowedUIDLength = 4;

    bool compareUID(byte* uid, byte uidSize,
                    byte* allowed, byte allowedSize) {
      if (uidSize != allowedSize) return false;
      for (byte i = 0; i < uidSize; i++) {
        if (uid[i] != allowed[i]) return false;
      }
      return true;
    }

    void setup() {
      Serial.begin(115200);
      while (!Serial) {;}

      Serial.println("RFID Access Demo - Present a card.");

      SPI.begin();
      rfid.PCD_Init();
      delay(50);

      Serial.print("Firmware version: 0x");
      Serial.println(
        rfid.PCD_ReadRegister(MFRC522::VersionReg),
        HEX
      );
    }

    void loop() {
      if (!rfid.PICC_IsNewCardPresent()) return;
      if (!rfid.PICC_ReadCardSerial()) return;

      Serial.println("Card detected!");

      // Print UID
      Serial.print("UID (HEX): ");
      for (byte i = 0; i < rfid.uid.size; i++) {
        if (rfid.uid.uidByte[i] < 0x10)
          Serial.print("0");
        Serial.print(rfid.uid.uidByte[i], HEX);
        Serial.print(" ");
      }
      Serial.println();

      // Check if this is the allowed card
      if (compareUID(
            rfid.uid.uidByte,
            rfid.uid.size,
            allowedUID,
            allowedUIDLength)) {
        Serial.println("✅ Access GRANTED (known card)");
      } else {
        Serial.println("❌ Access DENIED (unknown card)");
      }

      Serial.println("------------------------");

      rfid.PICC_HaltA();
      rfid.PCD_StopCrypto1();

      delay(200);
    }
      
Arduino / C++ XIAO SAMD21 + RC522 RFID (MIFARE Classic Write + Verify)

    #include <SPI.h>
    #include <MFRC522.h>

    const uint8_t SS_PIN  = 6;   // RC522 SDA -> XIAO D6
    const uint8_t RST_PIN = 7;   // RC522 RST -> XIAO D7

    MFRC522 rfid(SS_PIN, RST_PIN);
    MFRC522::MIFARE_Key key;

    const byte BLOCK_TO_WRITE = 4;  // Sector 1, Block 4 on a MIFARE 1K card

    // The string we want to store: "HTMAA 2025 TONY"
    byte dataBlock[16] = {
      'H', 'T', 'M', 'A', 'A', ' ', '2', '0',
      '2', '5', ' ', 'T', 'O', 'N', 'Y', 0x00   // last byte = 0x00 padding
    };

    void printBlock(byte* buffer, byte bufferSize) {
      Serial.print("HEX:   ");
      for (byte i = 0; i < bufferSize; i++) {
        if (buffer[i] < 0x10) Serial.print("0");
        Serial.print(buffer[i], HEX);
        Serial.print(" ");
      }
      Serial.println();

      Serial.print("ASCII: ");
      for (byte i = 0; i < bufferSize; i++) {
        char c = (char)buffer[i];
        if (c >= 32 && c <= 126) {
          Serial.print(c);
        } else {
          Serial.print(".");
        }
      }
      Serial.println();
    }

    void setup() {
      Serial.begin(115200);
      while (!Serial) {;}

      Serial.println("=== MIFARE Write Demo ===");
      Serial.println("Will write 'HTMAA 2025 TONY' to Block 4 (Sector 1).");
      Serial.println("Present the card to the reader...");

      // Set default key = FF FF FF FF FF FF
      for (byte i = 0; i < 6; i++) {
        key.keyByte[i] = 0xFF;
      }

      SPI.begin();
      rfid.PCD_Init();
      delay(50);

      Serial.print("Firmware version: 0x");
      Serial.println(rfid.PCD_ReadRegister(MFRC522::VersionReg), HEX);
    }

    void loop() {
      // Wait for a card
      if (!rfid.PICC_IsNewCardPresent()) return;
      if (!rfid.PICC_ReadCardSerial()) return;

      Serial.println("\nNew card detected!");

      // Print UID
      Serial.print("UID: ");
      for (byte i = 0; i < rfid.uid.size; i++) {
        if (rfid.uid.uidByte[i] < 0x10) Serial.print("0");
        Serial.print(rfid.uid.uidByte[i], HEX);
        Serial.print(" ");
      }
      Serial.println();

      MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak);
      Serial.print("Card type: ");
      Serial.println(rfid.PICC_GetTypeName(piccType));

      // Only proceed for MIFARE Classic types
      if (piccType != MFRC522::PICC_TYPE_MIFARE_1K &&
          piccType != MFRC522::PICC_TYPE_MIFARE_4K &&
          piccType != MFRC522::PICC_TYPE_MIFARE_MINI) {
        Serial.println("This is not a MIFARE Classic-type card. Aborting.");
        rfid.PICC_HaltA();
        rfid.PCD_StopCrypto1();
        return;
      }

      // --- Authenticate the block using Key A ---
      Serial.print("Authenticating block ");
      Serial.print(BLOCK_TO_WRITE);
      Serial.println(" with Key A...");

      MFRC522::StatusCode status;
      status = rfid.PCD_Authenticate(
        MFRC522::PICC_CMD_MF_AUTH_KEY_A,
        BLOCK_TO_WRITE,
        &key,
        &(rfid.uid)
      );

      if (status != MFRC522::STATUS_OK) {
        Serial.print("Authentication failed: ");
        Serial.println(rfid.GetStatusCodeName(status));
        rfid.PICC_HaltA();
        rfid.PCD_StopCrypto1();
        return;
      }

      Serial.println("Authentication success!");

      // --- Write data to the block ---
      Serial.println("Writing data to block...");
      status = rfid.MIFARE_Write(BLOCK_TO_WRITE, dataBlock, 16);
      if (status != MFRC522::STATUS_OK) {
        Serial.print("Write failed: ");
        Serial.println(rfid.GetStatusCodeName(status));
        rfid.PICC_HaltA();
        rfid.PCD_StopCrypto1();
        return;
      }

      Serial.println("Write success!");

      // --- Read back for verification ---
      byte readBuffer[18];
      byte size = sizeof(readBuffer);

      status = rfid.MIFARE_Read(BLOCK_TO_WRITE, readBuffer, &size);
      if (status != MFRC522::STATUS_OK) {
        Serial.print("Read-back failed: ");
        Serial.println(rfid.GetStatusCodeName(status));
      } else {
        Serial.println("Read-back data from block:");
        printBlock(readBuffer, 16);
      }

      // Clean up
      rfid.PICC_HaltA();
      rfid.PCD_StopCrypto1();

      Serial.println("\nRemove card and tap again if you want to rewrite.");
      delay(500);
    }